<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>force</title>
    <script src="js/d3@7.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/d3@7"></script> -->


</head>

<body>
    <svg height="400" width="500"></svg>
</body>


<script>
    /**
     * @description: 
     * x/y  节点的当前 x/y 坐标
     * vx/vy 节点的当前 x/y 方向速度
     * fx/fy 节点的固定 x/y 坐标
     * index 节点在节点数组中的索引位置。
     * source 边的起点节点
     * target 边的终点节点
     * value 边的权重或长度
     */
    //数据
    var nodes = [//节点集
        { name: "湖南邵阳" },
        { name: "山东莱州" },
        { name: "广东阳江" },
        { name: "山东枣庄" },
        { name: "赵丽泽" },
        { name: "王恒" },
        { name: "张欣鑫" },
        { name: "赵明山" },
        { name: "班长" }
    ];

    var edges = [//边集
        { source: 0, target: 4, relation: "籍贯", value: 1.3 },
        { source: 4, target: 5, relation: "舍友", value: 1 },
        { source: 4, target: 6, relation: "舍友", value: 1 },
        { source: 4, target: 7, relation: "舍友", value: 1 },
        { source: 1, target: 6, relation: "籍贯", value: 2 },
        { source: 2, target: 5, relation: "籍贯", value: 0.9 },
        { source: 3, target: 7, relation: "籍贯", value: 1 },
        { source: 5, target: 6, relation: "同学", value: 1.6 },
        { source: 6, target: 7, relation: "朋友", value: 0.7 },
        { source: 6, target: 8, relation: "职责", value: 2 }
    ];

    var margin = 30;//边距
    var svg = d3.select('svg');
    var width = svg.attr('width');
    var height = svg.attr('height');

    var g = svg.append('g').attr('transform', 'translate(' + margin + ',' + margin + ')');


    //新建一个颜色比例尺
    var scaleColor = d3.scaleOrdinal()
        .domain(d3.range(nodes.length))
        .range(d3.schemeCategory10);

    //新建一个力导向图
    var forceSimulation = d3.forceSimulation(nodes)//绑定节点数据
        .force("link", d3.forceLink(edges).distance(d => d.value * 100)) //指定连线数组并设置边长
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2))//设置图形 中心点
        .on('tick', linksTick);//在每个时间步调用 linksTick 函数。



    //再来看下顶点数据 和 边数据
    console.log(nodes);
    console.log(edges);


    //绘制边  这里注意一下绘制顺序  在d3中  各元素是有层级关系的，先绘制的在下面
    var links = g.append('g')
        .selectAll('line')
        .data(edges)
        .enter()
        .append('line')
        .attr('stroke', function (d, i) {
            return scaleColor(i);//设置边线颜色
        })
        .attr('stroke-width', '1');//设置边线宽度


    //绘制边上的文字
    var linksText = g.append('g')
        .selectAll('text')
        .data(edges)
        .enter()
        .append('text')
        .text(function (d, i) {
            return d.relation;
        });


    //创建节点分组
    var gs = g.selectAll('.circle')//选择所有圆形元素，并绑定节点数据。
        .data(nodes)
        .enter()
        .append('g')
        .attr('transform', function (d, i) {
            return 'translate(' + d.x + ',' + d.y + ')'
        })
        .call(
            d3.drag()//相当于移动端的拖拽手势  分以下三个阶段
                .on('start', start)
                .on('drag', drag)
                .on('end', end)
        );

    //绘制节点
    gs.append('circle')
        .attr('r', 10)
        .attr('fill', function (d, i) {
            return scaleColor(i);
        });

    //绘制文字
    gs.append('text')
        .text(function (d, i) {
            return d.name;
        });




    // 更新边的起点 终点 ,边上文字 的坐标。更新节点的位置。
    function linksTick() {
        links
            .attr("x1", function (d) {
                // console.log(d, "aa");
                return d.source.x;
            })
            .attr("y1", function (d) { return d.source.y; })
            .attr("x2", function (d) { return d.target.x; })
            .attr("y2", function (d) { return d.target.y; });

        linksText
            .attr("x", function (d) {
                return (d.source.x + d.target.x) / 2;
            })
            .attr("y", function (d) {
                return (d.source.y + d.target.y) / 2;
            });

        gs && gs.attr('transform', function (d, i) {
            return 'translate(' + d.x + ',' + d.y + ')';
        })

    }

    // 拖拽开始时触发的事件。
    function start(event, d) {
        //检查当前是否有其他活动的拖拽手势。如果没有（event.active 为 0），则重新启动力模拟，并设置较高的 alphaTarget 值，使节点位置更新更快。
        if (!event.active) {//event.active 属性对判断并发的拖拽手势序列中的 start 事件和 end 事件: 在拖拽手势开始时为0，在拖拽结束最后一个手势事件时为0
            //这里就是drag的过程中
            forceSimulation.alphaTarget(0.8).restart();
            //设置衰减系数，对节点位置移动过程的模拟，数值越高移动越快，数值范围[0，1]
            // restart：重新启动力模拟 可以确保力模拟重新开始运行。
        }
        d.fx = d.x;
        d.fy = d.y;
    }
    // 拖拽进行时触发的事件。
    function drag(event, d) {
        d.fx = event.x;
        d.fy = event.y;
    }

    function end(event, d) {
        if (!event.active) {
            forceSimulation.alphaTarget(0);
        }
        d.fx = null;
        d.fy = null;
    }


</script>

</html>